﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Services.Clab.UI.Mvvmc.Properties;

namespace Microsoft.Services.Clab.UI.Mvvmc
{
    public sealed class ControllerCollection : ObservableCollection<IController>
    {
        private IController ownerController;
        private Action<ControllerState> ownerSetStateFunc;
        [ThreadStatic]
        private bool removing;
        [ThreadStatic]
        private bool internalAdd;

        public ControllerCollection(IController owner, Action<ControllerState> ownerSetStateFunc)
        {
            this.ownerController = owner;
            this.ownerSetStateFunc = ownerSetStateFunc;
        }

        public IController Find(Guid controllerId)
        {
            return this.FirstOrDefault(c => c.ControllerId == controllerId);
        }

        public void Create(string logicalName, object initParameters = null, Action<IController> onBeforeAdd = null, Action<IController> onInitializedAction = null, Action<Exception> onException = null)
        {
            ServiceLocator.Current.GetInstance<IControllerFactory>().CreateController(
                logicalName,
                initParameters: initParameters,
                parentController: this.ownerController,
                onCreatingAction: c =>
                {
                    ownerSetStateFunc(ControllerState.Busy);
                },
                onCreatedAction: c =>
                {
                    if (onBeforeAdd != null)
                    {
                        onBeforeAdd(c);
                    }

                    try
                    {
                        internalAdd = true;
                        this.Add(c);
                    }
                    finally
                    {
                        internalAdd = false;
                    }
                },
                onInitializedAction: onInitializedAction + (c => ownerSetStateFunc(ControllerState.Ready)),
                onException: onException);
        }

        public void Remove(Guid controllerId)
        {
            var item = this.Find(controllerId);
            if (item != null)
            {
                base.Remove(item);
            }
        }

        protected override void ClearItems()
        {
            while (this.Count > 0)
            {
                this.RemoveItem(0);
            }
        }

        protected override void InsertItem(int index, IController item)
        {
            if (this.internalAdd)
            {
                base.InsertItem(index, item);
            }
            else
            {
                throw new InvalidOperationException(Resources.AddNotSupportedOnControllerCollection);
            }
        }

        protected override void RemoveItem(int index)
        {
            try
            {
                this.removing = true;

                var item = base[index];
                if (item.State < ControllerState.Disposing)
                {
                    item.Dispose();
                }
                base.RemoveItem(index);
            }
            finally
            {
                this.removing = false;
            }
        }


        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnCollectionChanged(e);

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    var npc = item as INotifyPropertyChanged;
                    if (npc != null)
                    {
                        npc.PropertyChanged += OnItemPropertyChanged;
                    }
                }
            }

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    var npc = item as INotifyPropertyChanged;
                    if (npc != null)
                    {
                        npc.PropertyChanged -= OnItemPropertyChanged;
                    }
                }
            }
        }

        private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "State")
            {
                var item = sender as IController;
                if (item.State == ControllerState.Disposed && !removing)
                {
                    this.Remove(item);
                }
            }
        }
    }
}
